JNI 程序中的异常分为以下几种:
- Native 程序原生异常,一般通过函数返回值和 linux 信号处理, C++ 中也有 try catch 机制解决异常,不是本文重点
- JNIEnv 内部函数抛出的异常,一般通过返回值判断,发现异常直接 return, jvm 会给将异常传递给 Java 层
- Native 回调 Java 层方法,被回调的方法抛出异常,JNI 提供了特定的 API 来处理这类异常
# 1. JNIEnv 内部函数抛出的异常
很多 JNIEnv 中的函数都会抛出异常,处理方法大体上是一致的:
- 返回值与特殊值(一般是 NULL)比较,知晓函数是否发生异常
- 如果发生异常立即 return
- jvm 会将异常抛给 java 层,我们可以在 java 层通过 try catch 机制捕获异常
接着我们来看一个例子:
Java 层:
public native void exceptionTest();
//调用
try {
exceptionTest();
} catch (Exception e) {
e.printStackTrace();
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Native 层:
extern "C"
JNIEXPORT void JNICALL
Java_com_yuandaima_myjnidemo_MainActivity_exceptionTest(JNIEnv *env, jobject thiz) {
//查找的类不存在,返回 NULL;
jclass clazz = env->FindClass("com/yuandaima/myjnidemo/xxx");
if (clazz == NULL) {
return; //return 后,jvm 会向 java 层抛出 ClassNotFoundException
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
执行后的 log:
java.lang.ClassNotFoundException: Didn't find class "com.yuandaima.myjnidemo.xxx"
1
说明,java 层捕获到了异常
# 2. Native 回调 Java 层方法,被回调的方法抛出异常
Native 回调 Java 层方法,被回调的方法抛出异常。这样情况下一般有两种解决办法:
- Java 层 Try catch 本地方法,这是比较推荐的办法。
- Native 层处理异常,异常处理如果和 native 层相关,可以采用这种方式
# 2.1 Java 层 Try catch 本地方法
Java 层:
//执行这个方法会抛出异常
private static int exceptionMethod() {
return 20 / 0;
}
//native 方法,在 native 中,会调用到 exceptionMethod() 方法
public native void exceptionTest();
//Java 层调用
try {
exceptionTest();
} catch (Exception e) {
//这里处理异常
//一般是打 log 和弹 toast 通知用户
e.printStackTrace();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Native 层:
extern "C"
JNIEXPORT void JNICALL
Java_com_yuandaima_myjnidemo_MainActivity_exceptionTest(JNIEnv *env, jobject thiz) {
jclass clazz = env->FindClass("com/yuandaima/myjnidemo/TestJavaClass");
if (clazz == NULL) {
return;
}
//调用 java 层会抛出异常的方法
jmethodID static_method_id = env->GetStaticMethodID(clazz, "exceptionMethod", "()I");
if (NULL == static_method_id) {
return;
}
//直接调用,发生 ArithmeticException 异常,传回 Java 层
env->CallStaticIntMethod(clazz, static_method_id);
env->DeleteLocalRef(clazz);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2.2 Native 层处理异常
有的异常需要在 Native 处理,这里又分为两类:
- 异常在 Native 层就处理完了
- 异常在 Native 层处理了,还需要返回给 Java 层,Java 层继续处理
接着我们看下示例:
Java 层:
//执行这个方法会抛出异常
private static int exceptionMethod() {
return 20 / 0;
}
//native 方法,在 native 中,会调用到 exceptionMethod() 方法
public native void exceptionTest();
//Java 层调用
try {
exceptionTest();
} catch (Exception e) {
//这里处理异常
//一般是打 log 和弹 toast 通知用户
e.printStackTrace();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Native 层:
extern "C"
JNIEXPORT void JNICALL
Java_com_yuandaima_myjnidemo_MainActivity_exceptionTest(JNIEnv *env, jobject thiz) {
jthrowable mThrowable;
jclass clazz = env->FindClass("com/yuandaima/myjnidemo/TestJavaClass");
if (clazz == NULL) {
return;
}
jmethodID static_method_id = env->GetStaticMethodID(clazz, "exceptionMethod", "()I");
if (NULL == static_method_id) {
return;
}
env->CallStaticIntMethod(clazz, static_method_id);
//检测是否有异常发生
if (env->ExceptionCheck()) {
//获取到异常对象
mThrowable = env->ExceptionOccurred();
//这里就可以根据实际情况处理异常了
//.......
//打印异常信息堆栈
env->ExceptionDescribe();
//清除异常信息
//如果,异常还需要 Java 层处理,可以不调用 ExceptionClear,让异常传递给 Java 层
env->ExceptionClear();
//如果调用了 ExceptionClear 后,异常还需要 Java 层处理,我们可以抛出一个新的异常给 Java 层
jclass clazz_exception = env->FindClass("java/lang/Exception");
env->ThrowNew(clazz_exception, "JNI抛出的异常!");
env->DeleteLocalRef(clazz_exception);
}
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(mThrowable);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37